#include "dbmgr.h"

#include <QStringList>
#include <QFile>
#include <QDebug>

#include <iostream>

using namespace std;

const std::map<DBMgr::DB_TABLE, std::string> DBMgr::TABLE_NAME_MAP =
{
    {DBMgr::TABLE_TYPE, "t_type"},
    {DBMgr::TABLE_FILE, "t_file"},
    {DBMgr::TABLE_DEP, "t_dep"}
};

const std::map<DBMgr::DB_TABLE, std::string> DBMgr::TABLE_CREATESQL_MAP =
{
    {
        DBMgr::TABLE_TYPE,
        "CREATE TABLE IF NOT EXISTS t_type ( \
        id INTEGER PRIMARY KEY AUTOINCREMENT, \
        suffix TEXT NOT NULL, \
        enable INTEGER DEFAULT(1) \
        )"
    },
    {
        DBMgr::TABLE_FILE,
        "CREATE TABLE IF NOT EXISTS t_file ( \
        id INTEGER PRIMARY KEY AUTOINCREMENT, \
        fullpath TEXT NOT NULL, \
        basename TEXT NOT NULL, \
        type INTEGER, \
        isdirectory INTEGER, \
        parent INTEGER, \
        thirdparty INTEGER DEFAULT(0), \
        FOREIGN KEY(type) REFERENCES t_type(id) ON DELETE CASCADE \
        )"
    },
    {
        DBMgr::TABLE_DEP,
        "CREATE TABLE IF NOT EXISTS t_dep ( \
        id INTEGER PRIMARY KEY AUTOINCREMENT, \
        left INTEGER NOT NULL, \
        right INTEGER NOT NULL, \
        FOREIGN KEY(left) REFERENCES t_file(id) ON DELETE CASCADE, \
        FOREIGN KEY(right) REFERENCES t_file(id) ON DELETE CASCADE \
        )"
    }
};

DBMgr::DBMgr() :
    m_currentDB("")
{

}

DBMgr::~DBMgr()
{
    if (m_db.isOpen())
    {
        m_db.close();
    }
}

DBMgr::DB_ERR DBMgr::createDB(std::string &path)
{
    if (m_db.isOpen())
    {
        m_db.close();
    }

    if (QFile::exists(QString::fromStdString(path)))
    {
        return ERR_EXIST;
    }

    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setDatabaseName(QString::fromStdString(path));
    m_db.open();

    if (m_db.isOpenError())
    {
        return ERR_OTHER;
    }

    QSqlQuery q(m_db);

    for (auto &iter : TABLE_CREATESQL_MAP)
    {
        if (!q.exec(QString::fromStdString(iter.second)))
        {
            qWarning() << "DB EXEC ERROR:" << QString::fromStdString(iter.second);
            return ERR_OTHER;
        }
    }

    return ERR_OK;
}

DBMgr::DB_ERR DBMgr::openDB(std::string &path)
{
    if (m_db.isOpen())
    {
        m_db.close();
    }

    if (!QFile::exists(QString::fromStdString(path)))
    {
        return ERR_NOT_EXIST;
    }

    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setDatabaseName(QString::fromStdString(path));
    m_db.open();

    if (m_db.isOpenError())
    {
        return ERR_OTHER;
    }

    QStringList tables = m_db.tables();
    for (auto &iter : TABLE_NAME_MAP)
    {
        if (!tables.contains(QString::fromStdString(iter.second)))
        {
            qWarning() << "The DB" << QString::fromStdString(path) << "doesn't contain all tables.";
            return ERR_INVALID;
        }
    }

    qDebug() << m_db.driverName();
    qDebug() << m_db.driver()->hasFeature(QSqlDriver::Transactions);
    return ERR_OK;
}

bool DBMgr::insertType(std::string &suffix)
{
    static string SQL_INSERT_TYPE = "INSERT INTO t_type(suffix) VALUES(?)";

    QSqlQuery q(m_db);
    q.prepare(QString::fromStdString(SQL_INSERT_TYPE));
    q.addBindValue(QString::fromStdString(suffix));
    q.exec();
    return q.isActive();
}

int DBMgr::insertFile(std::string &fullpath, std::string &basename, int type, int isdirectory, int parent, int thirdparty)
{
    static string SQL_INSERT_FILE = "INSERT INTO t_file(fullpath, basename, type, isdirectory, parent, thirdparty) VALUES(?, ?, ?, ?, ?, ?)";

    QSqlQuery q(m_db);
    q.prepare(QString::fromStdString(SQL_INSERT_FILE));
    q.addBindValue(QString::fromStdString(fullpath));
    q.addBindValue(QString::fromStdString(basename));
    q.addBindValue(type);
    q.addBindValue(isdirectory);
    q.addBindValue(parent);
    q.addBindValue(thirdparty);
    q.exec();
    return (q.lastInsertId().isValid() ? q.lastInsertId().toInt() : -1);
}

bool DBMgr::insertDep(int left, int right)
{
    static string SQL_INSERT_DEP = "INSERT INTO t_dep(left, right) VALUES(?, ?)";

    QSqlQuery q(m_db);
    q.prepare(QString::fromStdString(SQL_INSERT_DEP));
    q.addBindValue(left);
    q.addBindValue(right);
    q.exec();
    return q.isActive();
}

int DBMgr::insertFiles(std::vector<string> &fullpaths, std::vector<string> &basenames, std::vector<int> &types, std::vector<int> &isdirectorys, std::vector<int> &parents, std::vector<int> &thirdpartys)
{
    static string SQL_INSERT_FILE = "INSERT INTO t_file(fullpath, basename, type, isdirectory, parent, thirdparty) VALUES(?, ?, ?, ?, ?, ?)";

    QSqlQuery q(m_db);
    q.prepare(QString::fromStdString(SQL_INSERT_FILE));

    QVariantList fullpathList;
    for (auto &iter : fullpaths)
    {
        fullpathList << QString::fromStdString(iter);
    }

    QVariantList basenameList;
    for (auto &iter : basenames)
    {
        basenameList << QString::fromStdString(iter);
    }

    QVariantList typeList;
    for (auto &iter : types)
    {
        typeList << iter;
    }

    QVariantList isdirectoryList;
    for (auto &iter : isdirectorys)
    {
        isdirectoryList << iter;
    }

    QVariantList parentList;
    for (auto &iter : parents)
    {
        parentList << iter;
    }

    QVariantList thirdpartyList;
    for (auto &iter : thirdpartys)
    {
        thirdpartyList << iter;
    }

    q.addBindValue(fullpathList);
    q.addBindValue(basenameList);
    q.addBindValue(typeList);
    q.addBindValue(isdirectoryList);
    q.addBindValue(parentList);
    q.addBindValue(thirdpartyList);

    q.execBatch();
    return (q.lastInsertId().isValid() ? q.lastInsertId().toInt() : -1);
}

std::shared_ptr<std::vector<TypeInfo> > DBMgr::queryType(int id, std::string &suffix)
{
    static string SQL_QUERY_TYPE = "SELECT id, suffix FROM t_type WHERE 1 = 1 ";
    if (id != -1)
    {
        SQL_QUERY_TYPE += (" AND id = " + to_string(id));
    }
    if (suffix != "")
    {
        SQL_QUERY_TYPE += (" AND suffix = " + '"' + suffix + '"');
    }

    QSqlQuery q(m_db);
    q.exec(QString::fromStdString(SQL_QUERY_TYPE));

    shared_ptr<vector<TypeInfo>> result = make_shared<vector<TypeInfo>>();
    TypeInfo item;
    while(q.next())
    {
        item.id = q.value("id").toInt();
        item.suffix = q.value("suffix").toString().toStdString();
        result->push_back(item);
    }

    return result;
}

std::shared_ptr<std::vector<FileInfo> > DBMgr::queryFile(int id, std::string &fullpath, std::string &basename, int type, int isdirectory, int parent, int thirdparty)
{
    static string SQL_QUERY_FILE = "SELECT id, fullpath, basename, type, thirdparty FROM t_file WHERE 1 = 1 ";
    if (id != -1)
    {
        SQL_QUERY_FILE += (" AND id = " + to_string(id));
    }
    if (fullpath != "")
    {
        SQL_QUERY_FILE += (" AND fullpath = " + '"' + fullpath + '"');
    }
    if (basename != "")
    {
        SQL_QUERY_FILE += (" AND basename = " + '"' + basename + '"');
    }
    if (type != -1)
    {
        SQL_QUERY_FILE += (" AND type = " + to_string(type));
    }
    if (isdirectory != -1)
    {
        SQL_QUERY_FILE += (" AND isdirectory = " + to_string(isdirectory));
    }
    if (parent != -1)
    {
        SQL_QUERY_FILE += (" AND parent = " + to_string(parent));
    }
    if (thirdparty != -1)
    {
        SQL_QUERY_FILE += (" AND thirdparty = " + to_string(thirdparty));
    }

    QSqlQuery q(m_db);
    q.exec(QString::fromStdString(SQL_QUERY_FILE));

    shared_ptr<vector<FileInfo>> result = make_shared<vector<FileInfo>>();
    FileInfo item;
    while(q.next())
    {
        item.id = q.value("id").toInt();
        item.fullpath = q.value("fullpath").toString().toStdString();
        item.basename = q.value("basename").toString().toStdString();
        item.type = q.value("type").toInt();
        item.thirdparty = q.value("thirdparty").toInt();
        result->push_back(item);
    }

    return result;
}

std::shared_ptr<std::vector<DepInfo> > DBMgr::queryDep(int id, int left, int right)
{
    static string SQL_QUERY_DEP = "SELECT id, left, right FROM t_dep WHERE 1 = 1 ";
    if (id != -1)
    {
        SQL_QUERY_DEP += (" AND id = " + to_string(id));
    }
    if (left != -1)
    {
        SQL_QUERY_DEP += (" AND left = " + to_string(left));
    }
    if (right != -1)
    {
        SQL_QUERY_DEP += (" AND right = " + to_string(right));
    }

    QSqlQuery q(m_db);
    q.exec(QString::fromStdString(SQL_QUERY_DEP));

    shared_ptr<vector<DepInfo>> result = make_shared<vector<DepInfo>>();
    DepInfo item;
    while(q.next())
    {
        item.id = q.value("id").toInt();
        item.left = q.value("left").toInt();
        item.right = q.value("right").toInt();
        result->push_back(item);
    }

    return result;
}

std::shared_ptr<std::vector<TypeInfo> > DBMgr::queryAllType()
{
    return queryType(-1, string(""));
}
